﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Workflows.Components.TextMining.Clustering
{
    /// <summary>
    /// 
    /// </summary>
    public class WeightedTermSimilarityCalculator:IDistanceCalculator 
    {
        #region IDistanceCalculator Members

        public double CalculateDistance(
            Dictionary<int, DocTermFreq> docTermFreqs1,
            Dictionary<int, DocTermFreq> docTermFreqs2, 
            ref Dictionary<int,int> contributingTerms)
        {
            if (docTermFreqs1 == null || docTermFreqs1.Count == 0 ||
                docTermFreqs2 == null || docTermFreqs2.Count == 0)
                return 0;

            double score = 0;
            double perfectScore = 0;
            foreach (int termID in docTermFreqs1.Keys)
            {
                if (docTermFreqs2.ContainsKey(termID))
                {
                    score +=
                        (docTermFreqs1[termID].Weight * docTermFreqs1[termID].Count +
                        docTermFreqs2[termID].Weight * docTermFreqs2[termID].Count);
                    contributingTerms.Add(
                        termID,
                        Math.Min(docTermFreqs1[termID].Count, docTermFreqs2[termID].Count));
                }

                perfectScore += docTermFreqs1[termID].Weight * docTermFreqs1[termID].Count;
            }
            foreach (int termID in docTermFreqs2.Keys)
            {
                perfectScore += docTermFreqs2[termID].Weight * docTermFreqs2[termID].Count;
            }
            return (double)(perfectScore - score) / perfectScore;
        }

        public double CalculateDistance(
            Dictionary<int, DocTermFreq> docTermFreqs1,
            Dictionary<int, DocTermFreq> docTermFreqs2, 
            Dictionary<int, double> termWeights,
            bool useNGramSimilarity, int ngramLen,
            ref Dictionary<int, int> contributingTerms)
        {
            if (docTermFreqs1 == null || docTermFreqs1.Count == 0 ||
                docTermFreqs2 == null || docTermFreqs2.Count == 0)
                return 0;

            double score = 0;
            double perfectScore = 0;
            Dictionary<int, Dictionary<int, double>> termSimilarityMatrix =
               TermSimilarityMatrix.BuildTermDistanceMatrixUsingNgram(
                   docTermFreqs1, docTermFreqs2);

            foreach (int termID in docTermFreqs1.Keys)
            {
                double termScore = 0;
                if (docTermFreqs2.ContainsKey(termID))
                {
                    termScore =
                        (docTermFreqs1[termID].Weight * docTermFreqs1[termID].Count +
                        docTermFreqs2[termID].Weight * docTermFreqs2[termID].Count);
                    
                    contributingTerms.Add(
                        termID,
                        Math.Min(docTermFreqs1[termID].Count, docTermFreqs2[termID].Count));
                }
                else if(useNGramSimilarity && ngramLen>0)
                {
                    Dictionary<int, double> similarTerms = termSimilarityMatrix[termID];
                    foreach (int similarTermID in similarTerms.Keys)
                    {
                        if (docTermFreqs2.ContainsKey(similarTermID) &&
                            docTermFreqs2[similarTermID].Count * docTermFreqs2[similarTermID].Weight > termScore)
                            termScore = (docTermFreqs1[termID].Weight*docTermFreqs1[termID].Count +
                                         docTermFreqs2[similarTermID].Count*docTermFreqs2[similarTermID].Weight);
                    }
                }

                score += termScore;
                double perfectTermScore =docTermFreqs1[termID].Weight * docTermFreqs1[termID].Count;
                perfectScore += perfectTermScore;
            }
            foreach (int termID in docTermFreqs2.Keys)
            {
                double perfectTermScore = docTermFreqs2[termID].Weight * docTermFreqs2[termID].Count;
                double termScore = 0;
                if (docTermFreqs1.ContainsKey(termID))
                {
                    // do nothing, already added
                }
                else if (useNGramSimilarity && ngramLen>0)
                {
                    Dictionary<int, double> similarTerms = termSimilarityMatrix[termID];
                    foreach (int similarTermID in similarTerms.Keys)
                    {
                        if (docTermFreqs1.ContainsKey(similarTermID) &&
                            docTermFreqs1[similarTermID].Count * docTermFreqs1[similarTermID].Weight > termScore)
                            termScore = (docTermFreqs2[termID].Weight * docTermFreqs2[termID].Count +
                                         docTermFreqs1[similarTermID].Count * docTermFreqs1[similarTermID].Weight);
                    }
                }
                perfectScore += perfectTermScore;
            }

            return (double)(perfectScore - score) / perfectScore;
        }

        public double CalculateDistance(
            Dictionary<int, DocTermFreq> docTermFreqs1,
            Dictionary<int, DocTermFreq> docTermFreqs2,
            Dictionary<int, double> termWeights, Dictionary<int, string> termStemmings1,
            Dictionary<string, List<int>> termStemmings2,
            ref Dictionary<int, int> contributingTerms)
        {
            if (docTermFreqs1 == null || docTermFreqs1.Count == 0 ||
                docTermFreqs2 == null || docTermFreqs2.Count == 0)
                return 0;

            double score = 0;
            double perfectScore = 0;

            foreach (int termID in docTermFreqs1.Keys)
            {
                double termScore = 0;
                if (docTermFreqs2.ContainsKey(termID))
                {
                    termScore =
                        (docTermFreqs1[termID].Weight * docTermFreqs1[termID].Count +
                        docTermFreqs2[termID].Weight * docTermFreqs2[termID].Count);

                    contributingTerms.Add(
                        termID,
                        Math.Min(docTermFreqs1[termID].Count, docTermFreqs2[termID].Count));
                }
                else 
                {
                    List<int> sharedTermIDsAll = termStemmings2[termStemmings1[termID]];
                    foreach (int similarTermID in sharedTermIDsAll)
                    {
                        if (docTermFreqs2.ContainsKey(similarTermID) &&
                            docTermFreqs2[similarTermID].Count * docTermFreqs2[similarTermID].Weight > termScore)
                            termScore = (docTermFreqs1[termID].Weight * docTermFreqs1[termID].Count +
                                         docTermFreqs2[similarTermID].Count * docTermFreqs2[similarTermID].Weight);
                    }
                }

                score += termScore;
                double perfectTermScore = docTermFreqs1[termID].Weight * docTermFreqs1[termID].Count;
                perfectScore += perfectTermScore;
            }
            foreach (int termID in docTermFreqs2.Keys)
            {
                double perfectTermScore = docTermFreqs2[termID].Weight * docTermFreqs2[termID].Count;
                double termScore = 0;
                if (docTermFreqs1.ContainsKey(termID))
                {
                    // do nothing, already added
                }
                else 
                {
                    List<int> sharedTermIDsAll = termStemmings2[termStemmings1[termID]];
                    foreach (int similarTermID in sharedTermIDsAll)
                    {
                        if (docTermFreqs1.ContainsKey(similarTermID) &&
                            docTermFreqs1[similarTermID].Count * docTermFreqs1[similarTermID].Weight > termScore)
                            termScore = (docTermFreqs2[termID].Weight * docTermFreqs2[termID].Count +
                                         docTermFreqs1[similarTermID].Count * docTermFreqs1[similarTermID].Weight);
                    }
                }
                perfectScore += perfectTermScore;
            }

            return (double)(perfectScore - score) / perfectScore;
        }

        #endregion
    }
}
